<?php

namespace app\home\lib;

use think\cache\driver\Redis;
use think\Db;

class WeiXin
{

    private $appid;
    private $mch_id;
    private $key;
    private $secret;
    private $return_url;
    private $SSLCERT_PATH = '/cert/apiclient_cert.pem';
    private $SSLKEY_PATH = '/cert/apiclient_key.pem';

    const OK = 0;
    const IllegalAesKey = -41001;
    const IllegalIv = -41002;
    const IllegalBuffer = -41003;
    const DecodeBase64Error = -41004;

    public function __construct()
    {
        $content = Db::name('pay_way')->where(['id' => 1])->field('appid,mchid,wx_key,appsecret')->find();
        $this->appid = $content['appid'];
        $this->mch_id = $content['mchid'];
        $this->key = $content['wx_key'];
        $this->secret = $content['appsecret'];
        $this->return_url = '';
    }

    //获取access——token和openid
    private function getAccessToken($code)
    {
        $data = curlGet("https://api.weixin.qq.com/sns/oauth2/access_token?appid={$this->appid}&secret={$this->secret}&code={$code}&grant_type=authorization_code");
        return $data;

    }

    /**
     * 获取用户信息
     *
     * @param [type] $code
     * @return void
     */
    public function jscode2session($code)
    {
        $info = $this->getAccessToken($code);
        if (!$info) {
            return false;
        }
        $info = json_decode($info, true);

        if (!isset($info['access_token']) || !$info['access_token']) {
            return false;
        }
        $data = curlGet("https://api.weixin.qq.com/sns/userinfo?access_token={$info['access_token']}&openid={$info['openid']}");
        if (!$data) {
            return false;
        }
        $data = json_decode($data, true);
        return $data;
    }

    /**
     * 下单
     *
     * @param string $orderNo
     * @param float $amount
     * @return void
     */
    public function getPredefinedOrder($dec, $attach, $orderNo, $amount, $notifyUrl, $openid)
    {
        $data['body'] = $dec;//支付商品描述
        $data['attach'] = $attach;//异步回调标记
        $data['out_trade_no'] = $orderNo;//支付订单号
        $data['total_fee'] = $amount;//支付金额
        $data['ip'] = request()->ip();//ip
        $data['notify_url'] = $notifyUrl;
        $data['openid'] = $openid;
        $payString = $this->order($data);
        return $payString;
    }

    /**
     * Undocumented function
     *
     * @param [type] $data
     * @param integer $type 支付渠道 1-APP支付，2-h5支付
     * @return void
     */
    public function order($data)
    {
        $params['body'] = $data['body']; //商品描述
        $params['out_trade_no'] = $data['out_trade_no']; //订单号
        $params['total_fee'] = (float)($data['total_fee'] * 100); //金额是以分为单位，除测试外，需乘以100
        $params['trade_type'] = 'JSAPI';    //交易类型，h5支付，默认如此
        // $params['scene_info'] = json_encode([]);   
        // $params['scene_info'] = '{"h5_info": {"type":"Wap","wap_url": "' . site_url . '","wap_name": "' . $data['body'] . '"}}';   //场景信息,h5固定
        $params['spbill_create_ip'] = $data['ip'];   //终端IP
        $params['appid'] = $this->appid;
        $params['mch_id'] = $this->mch_id;
        $params['openid'] = isset($data['openid']) ? $data['openid'] : '';
        $params['nonce_str'] = $this->genRandomString();    //随机字符串
        $params['notify_url'] = $data['notify_url'];  //通知地址
        //获取签名数据
        $params['sign'] = $this->MakeSign($params);   //签名
        // print_r( $params );
        $xml = $this->data_to_xml($params);
        $uri = 'https://api.mch.weixin.qq.com/pay/unifiedorder';    //请求地址
        $response = $this->postXmlCurl($uri, $xml);   //自定义封装的xml请求格式，文章最下面为参考postxml
        if (!$response) {
            return false;
        }
        $result = $this->xml_to_data($response);
        if (!empty($result['result_code']) && !empty($result['err_code'])) {
            return $this->error_code($result['err_code']);
        }
        if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
            $resignData = array(
                'appId' => $this->appid,
                'nonceStr' => $result['nonce_str'],
                'package' => 'prepay_id=' . $result['prepay_id'],
                'signType' => 'MD5',
                'timeStamp' => (string)time()
            );
            //二次签名；
            $resignData['sign'] = $this->MakeSign($resignData);
            return $resignData;
        }
        return false;
    }

    public function transformPayment($data)
    {
        $params['partner_trade_no'] = $data['partner_trade_no']; //订单号
        $params['amount'] = (float)($data['amount'] * 100); //金额是以分为单位，除测试外，需乘以100
        $params['spbill_create_ip'] = request()->ip();   //终端IP
        $params['mch_appid'] = 'wxd72beb8507b15053';
        $params['mchid'] = '1504311811';
        $params['openid'] = isset($data['openid']) ? $data['openid'] : '';
        $params['nonce_str'] = $this->genRandomString();    //随机字符串
        $params['check_name'] = 'NO_CHECK';  //是否检验用户姓名
        $params['desc'] = $data['desc'];  //备注
        //获取签名数据
        $params['sign'] = $this->MakeSignTest($params);   //签名
        $xml = $this->data_to_xml($params);
        $uri = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';    //请求地址
        $response = $this->postXmlSSLCurlTest($uri, $xml);   //自定义封装的xml请求格式，文章最下面为参考postxml
        if (!$response) {
            return false;
        }
        $result = $this->xml_to_data($response);
        if ($result['return_code'] == 'SUCCESS') {
            return $result;
        } else {
            return false;
        }
    }


    /**
     * 生成签名
     * @return 签名
     */
    private function MakeSignTest($params)
    {
        //签名步骤一：按字典序排序数组参数
        ksort($params);
        $string = $this->ToUrlParams($params);
        //签名步骤二：在string后加入KEY
        $string = $string . "&key=JSDA82J09DS832DS732DNM9X7Z632ZKD";
        //签名步骤三：MD5加密
        // echo $string;exit;
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }


    //需要使用证书的请求
    private function postXmlSSLCurlTest($url, $xml, $second = 30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理，如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        //设置证书
        //使用证书：cert 与 key 分别属于两个.pem文件
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, dirname(__FILE__) . '/cert1/apiclient_cert.pem');
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, dirname(__FILE__) . '/cert1/apiclient_key.pem');
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            echo "curl 出错，错误码:$error" . "<br>";
        }
    }


    /**
     * 退款
     *
     * @param [type] $out_trade_no
     * @param [type] $outRefundNo
     * @param [type] $amount
     * @param [type] $refundamount
     * @return void
     */
    public function refund($out_trade_no, $outRefundNo, $amount, $refundamount)
    {
        $param = array(
            'appid' => $this->appid,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->genRandomString(),
            'out_refund_no' => $outRefundNo,//退款订单号
            'out_trade_no' => $out_trade_no,//支付订单号
            'total_fee' => (float)($amount * 100),
            'refund_fee' => (float)($refundamount * 100),
            // 'notify_url'    => site_url.'/jsk/wxRefundCallback',
        );
        $param['sign'] = $this->MakeSign($param);
        $xmldata = $this->data_to_xml($param);
        $xmlresult = $this->postXmlSSLCurl('https://api.mch.weixin.qq.com/secapi/pay/refund', $xmldata);
        $result = $this->xml_to_data($xmlresult);
        return $result;
    }

    /**
     * 查询退款
     *
     * @param [type] $out_trade_no
     * @param [type] $outRefundNo
     * @param [type] $amount
     * @param [type] $refundamount
     * @return void
     */
    public function refundQuery($outRefundNo)
    {
        $param = array(
            'appid' => $this->appid,
            'mch_id' => $this->mch_id,
            'nonce_str' => $this->genRandomString(),
            'out_refund_no' => $outRefundNo,//退款订单号
            // 'notify_url'    => site_url.'/jsk/wxRefundCallback',
        );
        $param['sign'] = $this->MakeSign($param);
        $xmldata = $this->data_to_xml($param);
        $xmlresult = $this->postXmlCurl('https://api.mch.weixin.qq.com/pay/refundquery', $xmldata);
        $result = $this->xml_to_data($xmlresult);
        return $result;
    }

    /**
     * 生成签名
     * @return 签名
     */
    public function MakeSign($params)
    {
        //签名步骤一：按字典序排序数组参数
        ksort($params);
        $string = $this->ToUrlParams($params);
        //签名步骤二：在string后加入KEY
        $string = $string . "&key=" . $this->key;
        //签名步骤三：MD5加密
        // echo $string;exit;
        $string = md5($string);
        //签名步骤四：所有字符转为大写
        $result = strtoupper($string);
        return $result;
    }

    /**
     * 将参数拼接为url: key=value&key=value
     * @param   $params
     * @return  string
     */
    public function ToUrlParams($params)
    {
        $string = '';
        if (!empty($params)) {
            $array = array();
            foreach ($params as $key => $value) {
                $array[] = $key . '=' . $value;
            }
            $string = implode("&", $array);
        }
        return $string;
    }

    /**
     * 输出xml字符
     * @param   $params     参数名称
     * return   string      返回组装的xml
     **/
    public function data_to_xml($params)
    {
        if (!is_array($params) || count($params) <= 0) {
            return false;
        }
        $xml = "<xml>";
        foreach ($params as $key => $val) {
            if (is_numeric($val)) {
                $xml .= "<" . $key . ">" . $val . "</" . $key . ">";
            } else {
                $xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
            }
        }
        $xml .= "</xml>";
        return $xml;
    }

    /**
     * 将xml转为array
     * @param string $xml
     * return array
     */
    public function xml_to_data($xml)
    {
        if (!$xml) {
            return false;
        }
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $data;
    }

    /**
     * 产生一个指定长度的随机字符串,并返回给用户
     * @param type $len 产生字符串的长度
     * @return string 随机字符串
     */
    public function genRandomString($len = 32)
    {
        $chars = array(
            "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
            "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
            "w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
            "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
            "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
            "3", "4", "5", "6", "7", "8", "9"
        );
        $charsLen = count($chars) - 1;
        // 将数组打乱
        shuffle($chars);
        $output = "";
        for ($i = 0; $i < $len; $i++) {
            $output .= $chars[mt_rand(0, $charsLen)];
        }
        return $output;
    }

    /**
     * 错误代码
     * @param  $code       服务器输出的错误代码
     * return string
     */
    public function error_code($code)
    {
        $errList = array(
            'NOAUTH' => '商户未开通此接口权限',
            'NOTENOUGH' => '用户帐号余额不足',
            'ORDERNOTEXIST' => '订单号不存在',
            'ORDERPAID' => '商户订单已支付，无需重复操作',
            'ORDERCLOSED' => '当前订单已关闭，无法支付',
            'SYSTEMERROR' => '系统错误!系统超时',
            'APPID_NOT_EXIST' => '参数中缺少APPID',
            'MCHID_NOT_EXIST' => '参数中缺少MCHID',
            'APPID_MCHID_NOT_MATCH' => 'appid和mch_id不匹配',
            'LACK_PARAMS' => '缺少必要的请求参数',
            'OUT_TRADE_NO_USED' => '同一笔交易不能多次提交',
            'SIGNERROR' => '参数签名结果不正确',
            'XML_FORMAT_ERROR' => 'XML格式错误',
            'REQUIRE_POST_METHOD' => '未使用post传递参数 ',
            'POST_DATA_EMPTY' => 'post数据不能为空',
            'NOT_UTF8' => '未使用指定编码格式',
        );
        if (array_key_exists($code, $errList)) {
            return $errList[$code];
        }
    }


    //xml请求
    public function postXmlCurl($url, $xml, $second = 30)
    {
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        //设置 header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        //post 提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行 curl
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            echo "curl 出错，错误码:$error" . "<br>";
        }
    }

    //需要使用证书的请求
    private function postXmlSSLCurl($url, $xml, $second = 30)
    {
        $ch = curl_init();
        //超时时间
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);
        //这里设置代理，如果有的话
        //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
        //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        //设置证书
        //使用证书：cert 与 key 分别属于两个.pem文件
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLCERT, dirname(__FILE__) . $this->SSLCERT_PATH);
        //默认格式为PEM，可以注释
        curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
        curl_setopt($ch, CURLOPT_SSLKEY, dirname(__FILE__) . $this->SSLKEY_PATH);
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        $data = curl_exec($ch);
        //返回结果
        if ($data) {
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            echo "curl 出错，错误码:$error" . "<br>";
        }
    }

}